#include "stdafx.h"
#include "getopt.h"

namespace cpputils
{

using namespace command_line_options_impl;

command_line_option_error::command_line_option_error(const string& Msg)
	: runtime_error(Msg)
{
}

command_line_argument::command_line_argument(const string& SetOption, const string& SetArg)
	: option(SetOption), argument(SetArg)
{
}

void command_line_options::clear()
{
	reset_vector(Options);
}

bool command_line_options::empty() const
{
	return Options.empty();
}

int command_line_options::size() const
{
	return Options.size();
}

const command_line_option& command_line_options::operator[](int I) const
{
	return Options[I];
}

command_line_options& command_line_options::add(const string& Name)
{
	Options.push_back(command_line_option());
	Current = Options.size() - 1;
	return add_name(Name);
}

command_line_options& command_line_options::add_name(const string& Name)
{
	if (Name.empty()) throw runtime_error("Option name can not be empty");
	Options[Current].names.push_back(Name);
	return *this;
}

command_line_options& command_line_options::set_argument(const string& Argument, const string& Value)
{
	Options[Current].argument = Argument;
	Options[Current].default_value = Value;
	return *this;
}

command_line_options& command_line_options::set_info(const string& Info)
{
	Options[Current].info = Info;
	return *this;
}

void command_line_options::show_help() const
{
	if (Options.empty()) return;
	checked_printf("Options:\n");
	for (int I = 0; I < (int)Options.size(); I++) show_help(Options[I]);
}

void command_line_options::show_help(const command_line_option& Option) const
{
	int Cursor = 0;
	Cursor += checked_printf("  ");
	for (int I = 0; I < (int)Option.names.size(); I++)
	{
		if (I > 0) Cursor += checked_printf(",");
		Cursor += checked_printf("--%s", Option.names[I].c_str());
	}
	if (Option.argument.empty())
		Cursor += checked_printf(" ");
	else
		if (Option.default_value.empty())
			Cursor += checked_printf("=%s ", Option.argument.c_str());
		else
			Cursor += checked_printf("[=%s] ", Option.argument.c_str());
	indented_print(30, Option.info, Cursor);
}

vector<command_line_argument> command_line_options::parse(int Argc, char* Argv[]) const
{
	vector<getopt::option> P;
	getopt::option Opt;
	for (int I = 0; I < (int)Options.size(); I++)
		for (int J = 0; J < (int)Options[I].names.size(); J++)
		{
			Opt.name = Options[I].names[J].c_str();
			if (Options[I].argument.empty()) Opt.has_arg = no_argument;
			else if (Options[I].default_value.empty()) Opt.has_arg = required_argument;
			else Opt.has_arg = optional_argument;
			Opt.flag = 0;
			Opt.val = I;
			P.push_back(Opt);
		}
	memset(&Opt, 0, sizeof Opt);
	P.push_back(Opt);
	getopt::opterr = 1;
	getopt::optind = 1;
	vector<command_line_argument> Args;
	for (;;)
	{
		int Index = -1;
		if (getopt::getopt_long(Argc, Argv, "", begin_ptr(P), &Index) < 0) break;
		assert(Index >= 0);
		command_line_argument Arg;
		Arg.option = P[Index].name;
		if (P[Index].has_arg != no_argument)
			if (getopt::optarg != 0) Arg.argument = getopt::optarg;
			else Arg.argument = Options[P[Index].val].default_value;
		Args.push_back(Arg);
	}
	for (int I = getopt::optind; I < Argc; I++)
		Args.push_back(command_line_argument("", Argv[I]));
	return Args;
}

vector<command_line_argument> command_line_options::parse(subarray<const string> Args) const
{
	int Argc = Args.size() + 1;
	vector<char*> Argv(Argc);
	Argv[0] = 0;
	for (int I = 1; I < Argc; I++) Argv[I] = (char*)Args[I - 1].c_str();
	return parse(Argc, begin_ptr(Argv));
}

command_line_options_handler::command_line_options_handler()
	: Argc(0), Argv(0)
{
}

command_line_options_handler& command_line_options_handler::add_usage(const string& S)
{
	Usages.push_back(S);
	return *this;
}

command_line_options& command_line_options_handler::options()
{
	return Options;
}

void command_line_options_handler::set_front_info(const string& S)
{
	FrontInfo = S;
}

void command_line_options_handler::set_back_info(const string& S)
{
	BackInfo = S;
}

void command_line_options_handler::set_arguments(int SetArgc, char* SetArgv[])
{
	assert(SetArgc >= 1);
	Argc = SetArgc;
	Argv = SetArgv;
	ProgName = extract_program_name(Argv[0]);
}

const string& command_line_options_handler::get_program_name() const
{
	return ProgName;
}

void command_line_options_handler::show_usages() const
{
	int Indent = 0;
	for (int I = 0; I < (int)Usages.size(); I++)
	{
		if (I == 0) Indent = checked_printf("Usage: ");
		else checked_printf("%*s", Indent, "or: ");
		indented_print(Indent, ProgName + " " + Usages[I], Indent);
	}
}

void command_line_options_handler::show_help() const
{
	show_usages();
	indented_print(2, FrontInfo);
	Options.show_help();
	checked_printf("\n");
	indented_print(2, BackInfo);
}

vector<command_line_argument> command_line_options_handler::parse() const
{
	return Options.parse(Argc, Argv);
}

string command_line_options_handler::extract_program_name(const string& Path) const
{
	for (int I = Path.length() - 1; I >= 0; I--)
		if (Path[I] == '/' || Path[I] == '\\') return Path.substr(I + 1);
	return Path;
}

namespace command_line_options_impl
{

	void indented_print(int Indent, const string& S, int Cursor)
	{
		assert(Indent >= 0);
		stringstream Str(S);
		string Word;
		while (!Word.empty() || Str)
		{
			Cursor %= ConsoleWidth;
			if (Cursor < Indent) Cursor += checked_printf("%*s", Indent - Cursor, "");
			if (Word.empty())
				if (Str.peek() == '\n' || Str.peek() == '\t' || Str.peek() == ' ') Word = Str.get();
				else Str >> Word;
				if (Word == "\n" || ConsoleWidth - Cursor < (int)Word.length() && (int)Word.length() <= ConsoleWidth - Indent)
				{
					checked_printf("\n");
					Cursor = 0;
					if (Word == "\n") Word.clear();
				}
				else
				{
					Cursor += checked_printf("%s", Word.c_str());
					Word.clear();
				}
		}
		if (Cursor % ConsoleWidth > 0) checked_printf("\n");
	}

}

}
